---
title: Contentful & Astro
description: Agrega contenido a tu proyecto Astro usando Contentful como CMS
type: cms
service: Contentful
i18nReady: true
---
import { FileTree } from '@astrojs/starlight/components';
import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'



[Contentful](https://www.contentful.com/) es un CMS headless que te permite administrar contenido, integrar con otros servicios y publicar en múltiples plataformas.

## Integración con Astro

En esta sección, usaremos el [SDK de Contentful](https://github.com/contentful/contentful.js) para conectar tu espacio de Contentful con Astro sin JavaScript en el lado del cliente.

### Prerrequisitos

Para empezar, necesitarás lo siguiente:

1. **Un proyecto Astro** - Si aún no tienes un proyecto Astro, nuestra [guía de instalación](/es/install/auto/) te ayudará a comenzar en cuestión de minutos.

2. **Una cuenta de Contentful y un espacio de Contentful**. Si aún no tienes una cuenta, puedes [registrarte](https://www.contentful.com/sign-up/) para obtener una cuenta gratuita y crear un nuevo espacio de Contentful. También puedes usar un espacio existente si ya tienes uno.

3. **Credenciales de Contentful** - Puedes encontrar las siguientes credenciales en tu panel de control de Contentful **Settings > API keys**. Si no tienes ninguna clave de API, crea una seleccionando **Add API key**.

    - **ID del espacio de Contentful** - El ID de tu espacio de Contentful.
    - **Token de acceso de entrega de Contentful** - El token de acceso para consumir contenido _publicado_ de tu espacio de Contentful.
    - **Token de acceso de previsualización de Contentful** - El token de acceso para consumir contenido _no publicado_ de tu espacio de Contentful.

### Configurando credenciales

Para agregar las credenciales de tu espacio de Contentful a Astro, crea un archivo `.env` en la raíz de tu proyecto con las siguientes variables:

```ini title=".env"
CONTENTFUL_SPACE_ID=TU_ID_DE_ESPACIO
CONTENTFUL_DELIVERY_TOKEN=TU_TOKEN_DE_ENTREGA
CONTENTFUL_PREVIEW_TOKEN=TU_TOKEN_DE_PREVISUALIZACION
```

Ahora, puedes usar estas variables de entorno en tu proyecto.

Si deseas tener IntelliSense para tus variables de entorno de Contentful, puedes crear un archivo `env.d.ts` en el directorio `src/` y configurar `ImportMetaEnv` de la siguiente manera:

```ts title="src/env.d.ts"
interface ImportMetaEnv {
  readonly CONTENTFUL_SPACE_ID: string;
  readonly CONTENTFUL_DELIVERY_TOKEN: string;
  readonly CONTENTFUL_PREVIEW_TOKEN: string;
}
```
:::tip
Lee más acerca de [usar variables de entorno](/es/guides/environment-variables/) y archivos `.env` en Astro.
:::

Tu directorio raíz ahora debería incluir estos nuevos archivos:

<FileTree title="Estructura del proyecto">
- src/
  - **env.d.ts**
- **.env**
- astro.config.mjs
- package.json
</FileTree>

### Instalando dependencias

Para conectar tu proyecto Astro con tu espacio de Contentful, instala los siguientes paquetes usando el comando de tu gestor de paquetes preferido:
- [`contentful.js`](https://github.com/contentful/contentful.js), el SDK oficial de Contentful para JavaScript
- [`rich-text-html-renderer`](https://github.com/contentful/rich-text/tree/master/packages/rich-text-html-renderer), un paquete para renderizar los campos de texto enriquecido de Contentful a HTML.

<PackageManagerTabs>
  <Fragment slot="npm">
  ```shell
  npm install contentful @contentful/rich-text-html-renderer
  ```
  </Fragment>
  <Fragment slot="pnpm">
  ```shell
  pnpm add contentful @contentful/rich-text-html-renderer
  ```
  </Fragment>
  <Fragment slot="yarn">
  ```shell
  yarn add contentful @contentful/rich-text-html-renderer
  ```
  </Fragment>
</PackageManagerTabs>

Después, crea un nuevo archivo llamado `contentful.ts` en el directorio `src/lib/` de tu proyecto.

```ts title="src/lib/contentful.ts"
import contentful from "contentful";

export const contentfulClient = contentful.createClient({
  space: import.meta.env.CONTENTFUL_SPACE_ID,
  accessToken: import.meta.env.DEV
    ? import.meta.env.CONTENTFUL_PREVIEW_TOKEN
    : import.meta.env.CONTENTFUL_DELIVERY_TOKEN,
  host: import.meta.env.DEV ? "preview.contentful.com" : "cdn.contentful.com",
});

```

La viñeta de código de arriba crea un nuevo cliente de Contentful, pasando las credenciales del archivo `.env`.

:::caution
Mientras estés en modo de desarrollo, tu contenido se obtendrá desde la **API de previsualización de Contentful**. Esto significa que podrás ver contenido no publicado desde la aplicación web de Contentful.

En el momento de la compilación, tu contenido se obtendrá desde la **API de entrega de Contentful**. Esto significa que solo el contenido publicado estará disponible en el momento de la compilación.
:::

Finalmente, tu directorio raíz ahora debería incluir estos nuevos archivos:

<FileTree title="Estructura del proyecto">
- src/
  - env.d.ts
  - lib/
    - **contentful.ts**
- .env
- astro.config.mjs
- package.json
</FileTree>

### Obteniendo datos

Los componentes de Astro pueden obtener datos de tu cuenta de Contentful usando el `contentfulClient` y especificando el `content_type`.

Por ejemplo, si tienes un tipo de contenido "blogPost" que tiene un campo de texto para un título y un campo de texto enriquecido para el contenido, tu componente podría verse así:

```astro
---
import { contentfulClient } from "../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { EntryFieldTypes } from "contentful";

interface BlogPost {
  contentTypeId: "blogPost",
  fields: {
    title: EntryFieldTypes.Text
    content: EntryFieldTypes.RichText,
  }
}

const entries = await contentfulClient.getEntries<BlogPost>({
  content_type: "blogPost",
});
---
<body>
  {entries.items.map((item) => (
    <section>
      <h2>{item.fields.title}</h2>
      <article set:html={documentToHtmlString(item.fields.content)}></article>
    </section>
  ))}
</body>
```

:::tip
Si tienes un espacio de Contentful vacío, consulta [configurar un modelo de Contentful](#configurando-un-modelo-de-contentful) para aprender cómo crear un modelo básico de blog para tu contenido.
:::

Puedes encontrar más opciones de consulta en la [documentación de Contentful](https://contentful.github.io/contentful.js/).

## Creando un blog con Astro y Contentful
 
Con la configuración de arriba, ahora puedes crear un blog que use Contentful como CMS.

### Prerequisitos

1. **Un espacio Contentful** - Para este tutorial recomendamos comenzar con un espacio vacío. Si ya tienes un modelo de contenido, puedes usarlo, pero deberás modificar nuestros fragmentos de código para que coincidan con tu modelo de contenido.
2. **Un proyecto Astro integrado con el [SDK de Contentful](https://github.com/contentful/contentful.js)** - Consulta [integrando con Astro](#integración-con-astro) para obtener más detalles sobre cómo configurar un proyecto Astro con Contentful.

### Configurando un modelo de Contentful

Dentro de tu espacio de Contentful, en la sección **Content model**, crea un nuevo modelo de contenido con los siguientes campos y valores:

- **Nombre:** Publicación de Blog
- **Identificador de API:** `blogPost`
- **Descripción:** Este tipo de contenido es para una publicación de blog

En el tipo de contenido recién creado, usa el botón **Add Field** para agregar 5 nuevos campos con los siguientes parámetros:

1. Campo de texto
    - **Nombre:** título 
    - **Identificador de API:** `title`
    (deja los otros parámetros con sus valores predeterminados)
2. Campo de fecha y hora
    - **Nombre:** fecha
    - **Identificador de API:** `date`
3. Campo de texto
    - **Nombre:** slug
    - **Identificador de API:** `slug`
    (deja los otros parámetros con sus valores predeterminados)
4. Campo de texto
    - **Nombre:** descripción
    - **Identificador de API:** `description`
5. Campo de texto enriquecido
    - **Nombre:** contenido
    - **Identificador de API:** `content`

Presiona **Save** para guardar los cambios.

En la sección **Content**  de tu espacion de Contentful, crea una nueva entrada haciendo clic en el botón **Add Entry**. Luego, completa los campos:

- **Title:** `¡Astro es increíble!`
- **Slug:** `astro-es-increíble`
- **Descripción:** `Astro es un nuevo generador de sitios estáticos que es increíblemente rápido y fácil de usar.`
- **Fecha:** `2022-10-05`
- **Content:** `¡Este es mi primer publicación blog!`

Presiona **Publish** para guardar la entrada. Acabas de crear tu primer post.

¡Siéntete libre de agregar tantos posts como quieras, luego cambia a tu editor de código favorito para comenzar a hackear con Astro!

### Mostrando una lista de publicaciones de blog

Crea una nueva interfaz llamada `BlogPost` y agrégala a tu archivo `contentful.ts` en `src/lib/`. Esta interfaz coincidirá con los campos de tu tipo de contenido de publicación de blog en Contentful. Lo usarás para tipar tu respuesta de entradas de publicaciones de blog.

```ts title="src/lib/contentful.ts" ins=", { EntryFieldTypes }" ins={3-12}
import contentful, { EntryFieldTypes } from "contentful";

export interface BlogPost {
  contentTypeId: "blogPost",
  fields: {
    title: EntryFieldTypes.Text
    content: EntryFieldTypes.RichText,
    date: EntryFieldTypes.Date,
    description: EntryFieldTypes.Text,
    slug: EntryFieldTypes.Text
  }
}

export const contentfulClient = contentful.createClient({
  space: import.meta.env.CONTENTFUL_SPACE_ID,
  accessToken: import.meta.env.DEV
    ? import.meta.env.CONTENTFUL_PREVIEW_TOKEN
    : import.meta.env.CONTENTFUL_DELIVERY_TOKEN,
  host: import.meta.env.DEV ? "preview.contentful.com" : "cdn.contentful.com",
});

```

Ahora, ve a la página de Astro donde obtendrás los datos de Contentful. En este ejemplo usaremos la página de inicio `index.astro` en `src/pages/`.

Importa la interfaz `BlogPost` y `contentfulClient` de `src/lib/contentful.ts`. 

Obtén todos los registros de Contentful con un tipo de contenido de `blogPost` mientras pasas la interfaz `BlogPost` para tipar tu respuesta.

```astro title="src/pages/index.astro"
---
import { contentfulClient } from "../lib/contentful";
import type { BlogPost } from "../lib/contentful";

const entries = await contentfulClient.getEntries<BlogPost>({
  content_type: "blogPost",
});
---
```

Esta llamada de búsqueda devolverá una arreglo de tus publicaciones en `entries.items`. Puedes usar `map()` para crear una nueva matriz (`posts`) que formatee tus datos devueltos.

El ejemplo de abajo devuelve las propiedades `items.fields` de nuestro modelo de contenido para crear una vista previa de la publicación del blog, y al mismo tiempo, reformatea la fecha a un formato más legible.

```astro title="src/pages/index.astro" ins={9-17}
---
import { contentfulClient } from "../lib/contentful";
import type { BlogPost } from "../lib/contentful";

const entries = await contentfulClient.getEntries<BlogPost>({
  content_type: "blogPost",
});

const posts = entries.items.map((item) => {
  const { title, date, description, slug } = item.fields;
  return {
    title,
    slug,
    description,
    date: new Date(date).toLocaleDateString()
  };
});
---
```

Finalmente, puedes usar `posts` en tu plantilla para mostrar una vista previa de cada publicación.

```astro astro title="src/pages/index.astro" ins={19-37}
---
import { contentfulClient } from "../lib/contentful";
import type { BlogPost } from "../lib/contentful";

const entries = await contentfulClient.getEntries<BlogPost>({
  content_type: "blogPost",
});

const posts = entries.items.map((item) => {
  const { title, date, description, slug } = item.fields;
  return {
    title,
    slug,
    description,
    date: new Date(date).toLocaleDateString()
  };
});
---
<html lang="es">
  <head>
    <title>Mi Blog</title>
  </head>
  <body>
    <h1>Mi Blog</h1>
    <ul>
      {posts.map((post) => (
        <li>
          <a href={`/posts/${post.slug}/`}>
            <h2>{post.title}</h2>
          </a>
          <time>{post.date}</time>
          <p>{post.description}</p>
        </li>
      ))}
    </ul>
  </body>
</html>
```

### Generando publicaciones de blog individuales

Usa el mismo método para obtener tus datos de Contentful que arriba, pero esta vez, en una página que creará una ruta de página única para cada entrada del blog.


#### Generación de sitios estáticos

Si estás usando el modo estático predeterminado de Astro, usarás [rutas dinámicas](/es/guides/routing/#rutas-dinámicas) y la función `getStaticPaths()`. Esta función se llamará en tiempo de compilación para generar la lista de rutas que se convierten en páginas.

Crea un archivo nuevo llamado `[slug].astro` en `src/pages/posts/`.

De la misma manera que importaste en `index.astro`, importa la interfaz `BlogPost` y `contentfulClient` de `src/lib/contentful.ts`.


Esta vez, obtén tus datos dentro de una función `getStaticPaths()`.

```astro title="src/pages/posts/[slug].astro"
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";

export async function getStaticPaths() {
  const entries = await contentfulClient.getEntries<BlogPost>({
    content_type: "blogPost",
  });
}
---
```

Luego, mapea cada elemento a un objeto con una propiedad `params` y `props`. La propiedad `params` se usará para generar la URL de la página y la propiedad `props` se pasará al componente de página como props.

```astro title="src/pages/posts/[slug].astro" ins={3,11-19}
---
import { contentfulClient } from "../../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { BlogPost } from "../../lib/contentful";

export async function getStaticPaths() {
  const entries = await contentfulClient.getEntries<BlogPost>({
    content_type: "blogPost",
  });

  const pages = entries.items.map((item) => ({
    params: { slug: item.fields.slug },
    props: {
      title: item.fields.title,
      content: documentToHtmlString(item.fields.content),
      date: new Date(item.fields.date).toLocaleDateString(),
    },
  }));
  return pages;
}
---
```

La propiedad dentro de `params` debe coincidir con el nombre de la ruta dinámica. Como nuestro nombre de archivo es `[slug].astro`, usamos `slug`.

En nuestro ejemplo, el objeto `props` pasa tres propiedades a la página:
- título (una cadena)
- contenido (un documento de texto enriquecido convertido a HTML usando `documentToHtmlString`)
- fecha (formateada usando el constructor `Date`)

Finalmente, puedes usar las propiedades de la página para mostrar tu publicación.

```astro title="src/pages/posts/[slug].astro" ins={21,23-32}
---
import { contentfulClient } from "../../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { BlogPost } from "../../lib/contentful";

export async function getStaticPaths() {
  const { items } = await contentfulClient.getEntries<BlogPost>({
    content_type: "blogPost",
  });
  const pages = items.map((item) => ({
    params: { slug: item.fields.slug },
    props: {
      title: item.fields.title,
      content: documentToHtmlString(item.fields.content),
      date: new Date(item.fields.date).toLocaleDateString(),
    },
  }));
  return pages;
}

const { content, title, date } = Astro.props;
---
<html lang="en">
  <head>
    <title>{title}</title>
  </head>
  <body>
    <h1>{title}</h1>
    <time>{date}</time>
    <article set:html={content} />
  </body>
</html>
```

Navega a http://localhost:4321/ y haz clic en uno de tus artículos para asegurarte de que tu ruta dinámica esté funcionando.

#### Renderizado del lado del servidor

Si has [optado por el modo SSR](/es/guides/server-side-rendering/), usarás una ruta dinámica que usa un parámetro `slug` para obtener los datos de Contentful.

Crea un archivo nuevo llamado `[slug].astro` en `src/pages/posts/`. Usa [`Astro.params`](/es/reference/api-reference/#astroparams) para obtener el slug de la URL, luego pásalo a `getEntries`:

```astro title="src/pages/posts/[slug].astro"
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";

const { slug } = Astro.params;

const data = await contentfulClient.getEntries<BlogPost>({
  content_type: "blogPost",
  "fields.slug": slug,
});
---
```

Si la entrada no fue encontrada, puedes redirigir al usuario a la página 404 usando [`Astro.redirect`](/es/guides/routing/#redirecciones-dinámicas).

```astro title="src/pages/posts/[slug].astro" ins={7, 12-13}
---
import { contentfulClient } from "../../lib/contentful";
import type { BlogPost } from "../../lib/contentful";

const { slug } = Astro.params;

try {
  const data = await contentfulClient.getEntries<BlogPost>({
    content_type: "blogPost",
    "fields.slug": slug,
  });
} catch (error) {
  return Astro.redirect("/404");
}
---
```
Para pasar los datos de la publicación a la sección de plantilla, crea un objeto `post` fuera del bloque `try/catch`.

Usa `documentToHtmlString` para convertir `content` de un documento a HTML, y usa el constructor de `Date` para formatear la fecha. `title` se puede dejar como está. Luego, agrega estas propiedades a tu objeto `post`.

```astro title="src/pages/posts/[slug].astro" ins={7,14-19}
---
import Layout from "../../layouts/Layout.astro";
import { contentfulClient } from "../../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { BlogPost } from "../../lib/contentful";

let post;
const { slug } = Astro.params;
try {
  const data = await contentfulClient.getEntries<BlogPost>({
    content_type: "blogPost",
    "fields.slug": slug,
  });
  const { title, date, content } = data.items[0].fields;
  post = {
    title,
    date: new Date(date).toLocaleDateString(),
    content: documentToHtmlString(content),
  };
} catch (error) {
  return Astro.redirect("/404");
}
---
```
  
Finalmente, puedes usar `post` para mostrar tu publicación en la sección de plantilla.

```astro title="src/pages/posts/[slug].astro" ins={24-33}
---
import Layout from "../../layouts/Layout.astro";
import { contentfulClient } from "../../lib/contentful";
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import type { BlogPost } from "../../lib/contentful";

let post;
const { slug } = Astro.params;
try {
  const data = await contentfulClient.getEntries<BlogPost>({
    content_type: "blogPost",
    "fields.slug": slug,
  });
  const { title, date, content } = data.items[0].fields;
  post = {
    title,
    date: new Date(date).toLocaleDateString(),
    content: documentToHtmlString(content),
  };
} catch (error) {
  return Astro.redirect("/404");
}
---
<html lang="en">
  <head>
    <title>{post?.title}</title>
  </head>
  <body>
    <h1>{post?.title}</h1>
    <time>{post?.date}</time>
    <article set:html={post?.content} />
  </body>
</html>
```

### Publicando tu sitio

Para desplegar tu sitio web, visita nuestras [guías de despliegue](/es/guides/deploy/) y sigue las instrucciones de tu proveedor de alojamiento preferido.

#### Reconstrucción al cambiar contenido en Contentful

Si tu proyecto esta usando el modo estático por defecto de Astro, necesitarás configurar un webhook para que se ejecute una nueva compilación cuando tu contenido cambie. Si estás usando Netlify o Vercel como proveedor de alojamiento, puedes usar su función de webhook para ejecutar una nueva compilación desde eventos de Contentful.

##### Netlify

Para configurar un webhook en Netlify:
 
1. Ve a tu panel de control y haz clic en **Build & deploy**.

2. En la pestaña **Continuous Deployment**, encuentra la sección **Build hooks** y haz clic en **Add build hook**.

3. Proporciona un nombre para tu webhook y selecciona la rama en la que quieres que se ejecute la compilación. Haz clic en **Save** y copia la URL generada.

##### Vercel

Para configurar un webhook en Vercel:
 
1. Ve a tu panel de control y haz clic en **Settings**.

2. En la pestaña **Git**, encuentra la sección **Deploy Hooks**.

3. Proporciona un nombre para tu webhook y selecciona la rama en la que quieres que se ejecute la compilación. Haz clic en **Add** y copia la URL generada.

##### Agregando un webhook a Contentful

En tu espacio de Contentful, ve a **settings** y haz clic en la pestaña **Webhooks**. Crea un nuevo webhook haciendo clic en el botón **Add Webhook**. Proporciona un nombre para tu webhook y pega la URL del webhook que copiaste en la sección anterior. Finalmente, haz clic en **Save** para crear el webhook.

Ahora, cada vez que publiques una nueva publicación de blog en Contentful, se iniciará una nueva compilación y se actualizará tu blog.
